// 创建WxRequest类
// 通过类的方式进行封装，会让代码更加具有复用性
// 也可以方便添加新的属性和方法
class WxRequest {

    // 定义实例属性，用来设置默认请求参数
    defaults = {
        baseURL: '',    // 请求基准地址
        url: '',    // 接口请求路径
        data: null, // 请求参数
        method: 'GET',  // 默认的请求方法
        header: {
            'Content-type': 'application/json', // 设置数据的交互格式
        },
        timeout: 6000,  // 默认请求超时时长，小程序默认的超时时长是1分钟
        isLoading: true,    // 控制是否使用默认的 loading，默认值是true，表示使用loading
    }

    // 定义拦截器对象
    // 需要包含请求拦截器以及响应拦截器，方便再请求之前以及响应之后进行逻辑处理
    interceptors = {
        // 请求拦截器
        request: (config) => config,
        // 响应拦截器
        response: (response) => response
    }

    // 定义数组队列
    // 初始值需要是一个空队列，用来存储请求
    queue = []

    // 在实例化时传入的参数，会被constructor形参进行接收
    constructor(params = {}) {
        // 通过Object.assign方法合并请求参数
        // 注意：需要传入的参数，覆盖默认的参数，因此传入的参数需要放在最后
        this.defaults = Object.assign({}, this.defaults, params)
    }

    /**
     * request 实例方法接收一个对象类型的参数
     * @param {*} options 属性值和wx.request方法调用时传递的参数保持一致
     */
    request(options) {
        // 如果有新请求，据清除上一次的定时器
        this.timerId && clearTimeout(this.timerId)

        // 合并请求参数
        options = { ...this.defaults, ...options }

        // 注意：需要先合并完整的请求地址 （baseURL + url）
        options.url = options.baseURL + options.url

        if (options.isLoading && options.method != "UPLOAD") {
            // 判断queue队列是否为空，如果是空，就显示loading
            // 如果不是空，就不显示loadding,，不调用 wx.showLoading
            this.queue.length === 0 && wx.showLoading()

            // 然后立即向 queue 数组队列中添加请求标识
            this.queue.push('request')
        }

        // 在请求发送之前，调用请求拦截器
        options = this.interceptors.request(options)

        // 需要使用Promise封装wx.request处理异步请求
        return new Promise((resolve, reject) => {
            if (options.method === 'UPLOAD') {
                wx.uploadFile({
                  ...options,
                  success: (res) => {
                    res.data = JSON.parse(res.data)
                    const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
                    resolve(this.interceptors.response(mergeRes))
                  },
                  fail: (err) => {
                    const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
                    reject(this.interceptors.response(mergeErr))
                  }
                })     
            } else {
                wx.request({
                    ...options,
                    // 当接口调用成功时会触发success回调函数
                    success: (res) => {
                        // 不管成功还是失败响应，都需要调用响应拦截器

                        // 在给响应拦截器传递参数时，需要将请求参数也一起传递
                        // 方便进行代码的调试或者其他逻辑处理，需要先合并参数
                        // 然后将合并的参数传递给响应拦截器
                        const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
                        resolve(this.interceptors.response(mergeRes))
                    },
                    // 当接口调用失败时会触发fail回调函数
                    fail: (err) => {
                        const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
                        reject(this.interceptors.response(mergeErr))
                    },
                    // 接口调用结束的回调函数（调用成功、失败都会执行）
                    complete: () => {
                        if (options.isLoading) {
                            this.queue.pop()
                            this.queue.length === 0 && this.queue.push('request')
                            this.timerId = setTimeout(() => {
                                this.queue.pop()
                                this.queue.length === 0 && wx.hideLoading({noConflict:true})
                                clearTimeout(this.timerId)
                            }, 1)
                        }
                    }
                })
            }
        })
    }

    /**
     * 封装GET实例方法
     * @param {*} url 请求的地址
     * @param {*} data 请求的参数
     * @param {*} config 其他配置
     */
    get(url, data = {}, config = {}) {
        // 需要调用request请求方法发送请求，只需要组织好参数，传递给request请求方法即可
        // 当调用get方法时，需要将request方法的返回值return出去
        return this.request(Object.assign({ url, data, method: 'GET' }, config))
    }

    /**
     * 封装DELETE实例方法
     * @param {*} url 请求的地址
     * @param {*} data 请求的参数
     * @param {*} config 其他配置
     */
    delete(url, data = {}, config = {}) {
        return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
    }

    /**
     * 封装POST实例方法
     * @param {*} url 请求的地址
     * @param {*} data 请求的参数
     * @param {*} config 其他配置
     */
    post(url, data = {}, config = {}) {
        return this.request(Object.assign({ url, data, method: 'POST' }, config))
    }

    /**
     * 封装PUT实例方法
     * @param {*} url 请求的地址
     * @param {*} data 请求的参数
     * @param {*} config 其他配置
     */
    put(url, data = {}, config = {}) {
        return this.request(Object.assign({ url, data, method: 'PUT' }, config))
    }

    // 用来处理并发请求
    all(...promise) {
        // 通过展开运算符接收传递的参数
        // 那么展开运算符会将传入的参数转成数组
        return Promise.all(promise)
    }

    /**
     * upload 实例方法，用来对 wx.uploadFile 进行封装
     * @param {*} url 文件的上传地址，接口地址
     * @param {*} filePath 要上传的文件资源路径
     * @param {*} name 文件对应的key
     * @param {*} config 其他配置
     */
    upload(url, filePath, name = 'file', config = {}) {
        return this.request(
            Object.assign({ url, filePath, name, method: 'UPLOAD' }, config)
        )
    }
}

export default WxRequest